'''
miscelaneous python utilities
'''
#from __future__ import with_statement
import sys, fileinput, os,logging



def make_dirs(d,children):
    #make child directories in each root directory
    if not(type(children)==dict):
        ot = dict(zip(children,children))
    else:
        ot = children
    ot.update({'*':''})
    for key,v in ot.items():
        ot.update({key:os.path.join(d,v)})
    #out = [os.path.join(d,o) for o in children]
    #out = dict(zip(['b','cell','cellhd'],self.out_paths))  
    for o in ot.values():
        try: 
            os.makedirs(o)
        except:
            continue
    return ot    

def copy_file(in_name, out_name, replace_dictionary=None):
    '''
    copy a file in same directory, with option to replace elements of text
    used for filling in seles scenario templates to be run by python
    '''
    input_file = open(in_name, 'r')
    output_file = open(out_name, 'w')
    line = input_file.readline()
    while line:
        line_alt = line
        if replace_dictionary:
            for key, value in replace_dictionary.items():
                line_alt=line_alt.replace(key, str(value))            
        output_file.write(line_alt)
        line = input_file.readline()
    input_file.close()
    output_file.close()
    
def run_seles(seles_path, scn_path):
    import subprocess
    process = subprocess.Popen([seles_path,"-p",scn_path])
    process.wait()

def run_cmd(args):
    import subprocess
    #os.system(args)
    process = subprocess.Popen(args,shell=True)
    process.wait()
    
def run_arcinfo(in_string):
    import subprocess
    process = subprocess.Popen(['arc',in_string])
    process.wait()

# #############
# file reading/dictionary making utilities
# from CS2125 assignment, originally
def type_convert(value):
    '''try converting a given value to an integer or floating point. Otherwhise leave value as it is.'''
    try:
        converted = int(value)
    except ValueError:
        try:
            converted = float(value)
        except ValueError:
            converted = value
    return converted    

def prompt_for_files(file_list_name,num_allowed='unlimited',default_file=''):
    '''
    prompts user for up to unlimited filenames (default None indicates no limit), with option to use default file 
    if supplied. returns a string if num_allowed = 1, a list of strings otherwise
    '''
    go = True
    while go:
        sys.stdout.write('Enter up to %s filenames for %s. \n' %(str(num_allowed),file_list_name))
        if default_file:
            sys.stdout.write('Press enter to use default file %s. \n' %(default_file))  
        if (num_allowed == 'unlimited') or (num_allowed > 1):
            sys.stdout.write('Enter multiple filenames on a single line, separated by spaces. \n')                       
        file_list_string = raw_input("%s: " %(file_list_name))
        if  file_list_string:
            file_list = file_list_string.split()
            file_list_len = len(file_list)
            if (num_allowed != 'unlimited') and (len(file_list) > num_allowed):
                sys.stdout.write('Too many filenames (%d). Max allowed is %d. \n' %(len(file_list),num_allowed))   
            else:
                go = False
        elif (not file_list_string) and default_file:
            file_list = default_file.split()
            sys.stdout.write('%s = %s \n' %(file_list_name,default_file))
            go = False
    if num_allowed ==1:
        file_list = file_list[0]
    return file_list

def check_files(file_list,var_name,prompt=True,check_dir =os.getcwd()):
    '''
    checks that specified files exist in specified directory, or prompts for valid names   
    accepts a single string (one file) or list of multiple files
    '''
    os.chdir(check_dir)    
    
    # if single string, convert to list
    if type(file_list) != list:
        in_list = False
    else:
        in_list = True
        
    if not in_list:
        file_list = [file_list]

    for i in range(len(file_list)):
        filename = file_list[i]
        go = True
        while go:
            try:
                file = open(filename)
                file.close()
                go = False
            except IOError:
                if prompt:
                    sys.stderr.write('In jh.check_files\n')
                    sys.stderr.write('%s file %s does not exist. Please enter correct file name. \n' %(var_name,filename)) 
                    filename = raw_input("%s: " %var_name)
                    if filename:
                        file_list[i] = filename
                    else:
                       raise IOError, 'File open error: %s'%(filename)
                else:
                    raise IOError, 'File open error: %s'%(filename)
    if not in_list and (len(file_list) == 1):
        file_list = file_list[0]
    return file_list

def load_table(path, header_set = None, header = False, delimiter = 'whitespace',strip_list = '',\
               ignore_list=['#'],max_lines=None, valtype=None):
    '''
    load delimited text file into dictionary of lists. Dictionary keys are column headers
    If header is True, first line of file is header row.
       Build dictionary h_head of 'col_name':list_index
    If header_set is included, check that required header names are among the header names in this input file.
    If strip_list is specified, listed characters will be removed from input... use mainly for stripping white space.
    All lines starting with  elements of ignore list except first line (if header = True) will be ignored.
    If type is specifies, all values in table will be converted to type
    '''
    #build set of first elements to ignore
    if type(ignore_list) != list:
        ignore_list = [ignore_list]
    ignore_set = set(ignore_list)

    #check that file can be opened    
    try:
        open(path)
    except:
        raise Exception, 'file could not be opened: %s'%path
    
    #code adapted from langtangen 2006, p68 
    file = open(path,'r')
    f = file.readlines()
    file.close()
    #with open(path,'r') as f:
    if 1:
        y = {}
        line_id = 0
        for line in f:
            if max_lines:
                if line_id > max_lines-1:
                    break        
            line_id += 1
    
            if delimiter == 'whitespace':
                line_list = line.strip('\n').strip('\r').split()
            else:
                line_list = line.strip('\n').strip('\r').split(delimiter)                   
            if len(strip_list):
                if type(strip_list) != list:
                    strip_list = [strip_list]
                for i in range(len(line_list)):
                   for strip_char in strip_list:
                       line_list[i] = line_list[i].replace(strip_char,'')
            
            if header and line_id == 1:
                #tolerate a header line starting with #
                line_list[0] = line_list[0].lstrip('#')
                ynames = line_list
                for name in ynames:
                    y[name] = []
                #check that necessary headers are included in header dictionary
                if header_set:
                    for col_name in header_set:
                        if not y.has_key(col_name):
                            raise Exception, 'Error loading input file %s. \n  Column %s not found. Check header row.'\
                                  %(path,col_name)
            elif set([line_list[0]]) & ignore_set:
                continue
            else:
                if header and (len(line_list) != len(y)):
                    raise Exception, 'Error loading input file %s. \n Number of cols in row %s does not match header row.'\
                          %(path, line_id)
                i = 0
                try: 
                    ynames
                except UnboundLocalError: 
                    ynames = range(len(line_list)) 
                    for name in ynames:
                        y[name] = []
                for name in ynames:
                    if valtype:
                        ev_string = valtype+'('+line_list[i]+')'
                        value = eval(ev_string)
                    else:
                        value = line_list[i]
                    y[name].append(value)
                    i += 1
    return y

def report_error(message,self=None,returnOnError = False):
    if self == None:
        raise Exception, message
    else:
        self.logger.error('!E!:'+message)
        return returnOnError    

def report_info(message,self=None):
    if self == None:
        print message
    else:
        self.logger.info(message)   
    
def write_matrix(path,file, h_mat, h_head = False, delimiter = '\t',self = None,append='w'):
    returnOnError = False
    '''
    write 2d list to delimited text file. Outer list row, inner list is col
    If h_head is True, first line of file is header row, written dictionary h_head of 'col_name':list_index
    '''

    full_path = '/'.join([path,file+'.txt'])
    #open file for writing, or report error
    try:
        os.chdir(path)
    except:
        os.makedirs(path)        
    try:
        output_file = open(full_path, append)
    except:
        return(report_error('jh.write_matrix: File could not be opened for writing: %s'%path),self,returnOnError)

    #write header row
    if append == 'w':
        out_list = [None]*len(h_head)
        for key,value in h_head.items():
            out_list[value] = key
        out_str = delimiter.join(out_list)+'\n'
        output_file.write(out_str)

    #iterate through h_mat
    for out_list in h_mat:
        out_list = list(out_list)
        for i in range(len(out_list)):
            out_list[i] = str(out_list[i])
        out_str = delimiter.join(out_list)+'\n'
        output_file.write(out_str)
    output_file.close()
    return True


def load_matrix(path, header_set = None, header = False, delimiter = 'whitespace',strip_list = '',ignore_list=['#'],\
                self = None):
    returnOnError = None,None
    '''
    load delimited text file into 2d list. Outer list row, inner list is col
    If header is True, first line of file is header row.
       Build dictionary h_head of 'col_name':list_index
    If header_set is included, check that required header names are among the header names in this input file.
    If strip_list is specified, listed characters will be stripped from input... use mainly for stripping white space.
    All lines starting with  elements of ignore list except first line (if header = True) will be ignored.
    '''
    #build set of first elements to ignore
    if type(ignore_list) != list:
        ignore_list = [ignore_list]
    ignore_set = set(ignore_list)
    if (header_set != None) and (type(header_set) != tuple) and (type(header_set) != list) and (type(header_set) != set):
        header_set = set([header_set])

    #check that file can be opened    
    try:
        open(path)
    except:
        return(report_error('file could not be opened: %s'%path),self,returnOnError)
    
    #iterate through file
    h_head = {}
    h_list = []
    for line in fileinput.input(path):
        if delimiter == 'whitespace':
            line_list = line.strip('\n').strip('\r').split()
        else:
            line_list = line.strip('\n').strip('\r').split(delimiter)
        if len(strip_list):
            if type(strip_list) != list:
                strip_list = [strip_list]
            for i in range(len(line_list)):
               for strip_char in strip_list:
                   line_list[i] = line_list[i].replace(strip_char,'')
        
        curr_lineno = fileinput.filelineno()    
        if header and (curr_lineno == 1):
            #tolerate a header line starting with #
            line_list[0] = line_list[0].lstrip('#')
            #build header dictionary
            for i in range(len(line_list)):
                temp = {line_list[i]:i}
                h_head.update(temp)
            #check that necessary headers are included in header dictionary
            if header_set:
                for col_name in header_set:
                    if not h_head.has_key(col_name):
                        msg = 'Error loading input file %s. \n  Column %s not found. Check header row.'\
                                            %(path,col_name)
                        return(report_error(msg,self,returnOnError))
        elif set([line_list[0]]) & ignore_set:
            continue
        else:
            if header and (len(line_list) != len(h_head)):
                msg ='Error loading input file %s. \n Number of cols in row %s does not match header row.'%\
                    (path, curr_lineno)
                return(report_error(msg,self,returnOnError))
            h_list.append(line_list) 
    fileinput.close()
    return h_list, h_head

def load_dictionary(matrix,i_key,i_value,value_type,process,no_data_set=False,transform = False):
    '''
    make dictionary from two columns of an input matrix
    matrix is input matrix name -> row is outer list, col in inner list
    i_key and i_value are indices of columns of dictionary key and values, respectively
    value_type is a sample of the desired type for values
    no_data_value is value for rows that will not be added to the dictionary.
    If process == 'count', dictionary will be a count of all instances of key. 
    If process == 'sum', dictionary will be a sum of values over all instances of key.
    If process == 'complain', more than 1 instance of key will generate an error.
    If process == 'replace', values of key will replace preceding values of key.    
    If process == 'index', values of key will be row id of that key.
    transform gives the option of a function to tranform values before they are added to the dictionary.
    '''
    
    #check if selected process is recognized
    known_processes = set(['count','sum','complain','replace','index'])
    if not (set([process]) & known_processes):
        sys.stderr.write('In common_functions.load_dictionary \n')
        sys.stderr.write('Selected process (%s) not recognized (in common_functions.load_dictionary()) \n' %(process))
    
    #build dictionary
    dictionary={}
    for row_id in range(len(matrix)):
        line_list = matrix[row_id]
        key = line_list[i_key]
        value_string = line_list[i_value]
        
        #check for duplicate key error, if that is a problem
        if ((process == 'complain') or (process == 'index')) and (dictionary.has_key(key)): 
            raise Exception, 'More than one instance of key (%s) found. \n'%(key)
        
        #skip no data cases
        if no_data_set != False:
            if type(no_data_set) != set:
                no_data_set = set([no_data_set])
            if no_data_set & set([value_string]):
                # if no value, skip to next line
                continue
        
        if process == 'count':
            if dictionary.has_key(key):
               value = dictionary[key]+1
            else:
               value = 1
        elif process == 'index':
            value = row_id
        elif (type(value_type) == str) and (process != 'sum') and (transform == False):
            #in this condition, tolerate entries of different type -- treat them all as strings
            value = value_string      
        else:
            value = type_convert(value_string)
            if type(value) != type(value_type):
                sys.stderr.write('In common_functions.load_dictionary \n')
                report_error('Problem with %s file format (%s). Value of type %s expected in column %d'\
                    %(filename,value_string,type(value_type),i_value))
                continue
            else:
                if transform:
                    value = transform(value)
                if (process == 'sum') and dictionary.has_key(key):
                    value = dictionary[key]+value
        temp = {key:value}
        dictionary.update(temp)
    return dictionary
